﻿using Bettzueche.RLLibrary.Types;
using System;
using System.Windows;

namespace AMEE1
{
    /// <summary>
    /// Konfigurationklasse mit Informationen zur disjunkten Partitionierung der Welt (des Zustands).
    /// </summary>
    /// <remarks>
    /// Konfiguration einer egozentrischen zeidimensionalen Karte mit Sektoren (Tortenstücke) und Spuren (Ringe), sodass
    /// radiale Abschnitte entstehen.<para>
    /// Bietet Hilfsmethoden:
    /// void GibSpurSektor(int abschnittsNummer, out int spur, out int sektor)
    /// int GibAbschnittsnummer(int spur, int sektor)
    /// Action GetAction(int abschnittsNummer): liefert voll ausgefülltes Action-Objekt (Aktion = "Gehe in Abschnitt n").
    /// </para>
    /// <para>
    /// Interne Hilfsmethode:
    /// int GetZielrichtung(Vector agentZiel): gibt die Sektornummer des Sektor, der in Richtung Ziel zeigt.
    /// </para>
    /// </remarks>
    /// <example>
    /// namespace NSExample {
    ///     class Example 
    ///     {
    ///         static void DoExample() {
    ///             // Karte mit zwei Spuren und vier Sektoren.
    ///             // Insgesamt 8 Abschnitte
    ///             int anzSektoren = 4;
    ///             float[] spurRadien = { 0.5f, 1.0f }; // zwei Spuren, jeweil der Breite 0.5
    ///             double precision = 1.0 / Math.Pow(2, 8); // 8 unterscheidbare Unterteilungen des Wertebereichs
    ///             int tilings = (int)Math.Pow(2, 2);
    ///             int[] entfernungen = { 1, 2, 3, 4, 5, 10, 20, 40, 80, Int32.MaxValue};
    ///             Entfernungen entfTiling = new Entfernungen(entfernungen);
    ///             MultiTiling uTiling = new MultiTiling(tilings, entfTiling, precision, min: 0.0, max: 1.0);
    ///             for (int spur = 0; spur &lt; 2; spur++) {
    ///                 for (int sektor = 0; sektor &lt; 4; sektor++) {
    ///                     int abschnittsNummer = weltConfig.GibAbschnittsnummer(spur,sektor);
    ///                     Console.WriteLine("Abschnitt Nummer: {0} (Sp: {1} Se: {2}", abschnittsNummer, spur, sektor);
    ///                 }
    ///     }   }   }
    ///     /* Ausgabe
    ///     Abschnitt Nummer: 0 (Sp: 0 Se: 0)
    ///     Abschnitt Nummer: 1 (Sp: 0 Se: 1)
    ///     Abschnitt Nummer: 2 (Sp: 0 Se: 2)
    ///     Abschnitt Nummer: 3 (Sp: 0 Se: 3)
    ///     Abschnitt Nummer: 4 (Sp: 1 Se: 0)
    ///     Abschnitt Nummer: 5 (Sp: 1 Se: 1)
    ///     Abschnitt Nummer: 6 (Sp: 1 Se: 2)
    ///     Abschnitt Nummer: 7 (Sp: 1 Se: 3)
    ///     */
    /// } 
    /// </example>
    class MapConfiguration
    {
        /// <summary>
        /// Radien der Spuren (vom Zentrum der egozentrischen Karte 
        /// <see cref="RLProject1.Model.AMEEWorld.WorldImage">WorldImage</see>)<para>
        /// Readonly Referenz auf das float-Array. Änderung der Werte kann zu Daten-Inkonsistenz führen!</para>
        /// </summary>
        internal readonly float[] SpurRadien;

        /// <summary>Maximale Schrittweite (in einen äußeren Abschnitt gehen)</summary>
        internal readonly double Schrittweite;

        /// <summary>2.0 * Math.PI</summary>
        static readonly double ZwoPI = 2.0 * Math.PI;

        #region Properties

        /// <summary>
        /// Gibt Gesamtzahl Sektoren.
        /// </summary>
        /// <value>
        /// The sektoren.
        /// </value>
        /// <remarks>Seiteneffekt: Set führt zur Neuberechnung von Dimension.</remarks>
        public int Sektoren {
            get { return _sektoren; }
            private set { _sektoren = value; /*calcDimension();*/ }
        }
        int _sektoren;

        /// <summary>
        /// Gibt Gesamtzahl Spuren
        /// </summary>
        /// <value>
        /// The spuren.
        /// </value>
        /// <remarks>Seiteneffekt: Set fhrt zur Neuberechnung von Dimension.</remarks>
        public int Spuren {
            get { return _spuren; }
            private set { _spuren = value; /*calcDimension();*/ }
        }
        int _spuren;

        /// <summary>
        /// Gibt die Gesamtzahl Abschnitte. Äquivalent zu Sektoren * Spuren.
        /// </summary>
        public int Abschnitte {
            get { return this.Sektoren * this.Spuren; }
        }

        /// <summary>
        /// Gibt die Gesamtzahl an Zielrichtungen. Äquivalent zu Sektoren.
        /// </summary>
        /// <value>
        /// The zielrichtungen.
        /// </value>
        public int Zielrichtungen {
            get { return this.Sektoren; }
        }

        /// <summary>
        /// Gibt die Anzahl möglicher Aktionen.<para>
        /// Im einfachen Standardfall (wie hier) ist diese äquivalent zu Tiling.Abschnitte</para>
        /// </summary>
        public int Aktionen { get; private set; }

        /// <summary>
        /// Gibt die Sektorbreite in Radiant. Ist Abhängig von '<c>Sektoren</c>'. Minus Unendlich, falls '<c>Sektoren</c>' negativ oder Null.
        /// </summary>
        internal double Sektorbreite {
            get {
                if (Sektoren <= 0)
                    return Double.NegativeInfinity;
                return ZwoPI / Sektoren;
            }
        }

        /// <summary>
        /// Gets the bumpiness tilings organisation.
        /// </summary>
        /// <value>
        /// The bumpiness tiling.
        /// </value>
        public MultiTiling BumpinessTiling { get; private set; }

        public Entfernungen EntfernungsTiling { get; private set; }

        #endregion


        #region Constructor(s)

        /// <summary>
        /// Initializes a new instance of the <see cref="MapConfiguration" /> class.
        /// </summary>
        /// <param name="uTiling">Tilings-Organisation der Unebenheitsmaße.</param>
        /// <param name="sektoren">The Anzahl Sektoren (Kreisabschnitte / Tortenstücke).
        /// <para>
        /// Bei 0 oder negativ -&gt; Sektoren = 0</para></param>
        /// <param name="spurRadien">Die Spurradien. Länge des Arrays wird zu Anzahl 'Spuren'.
        /// <para>
        /// Bei null -&gt; 'Spuren' = 0, 'SpurRadien'=leeres Array</para></param>
        /// <exception cref="System.ArgumentNullException">uTiling</exception>
        public MapConfiguration(MultiTiling uTiling, Entfernungen eTiling, int sektoren, params float[] spurRadien) {
            if (uTiling == null) {
                throw new ArgumentNullException("uTiling");
            }
            if (eTiling == null) {
                throw new ArgumentNullException("eTiling");
            }
            BumpinessTiling = uTiling;
            EntfernungsTiling = eTiling;
            Sektoren = sektoren > 0 ? sektoren : 0;
            Spuren = spurRadien != null ? spurRadien.Length : 0;
            this.Aktionen = this.Abschnitte; // standard Aktionen = 'Gehe zu Abschnitt xy'
            SpurRadien = spurRadien != null ? spurRadien : new float[0];

            Schrittweite = BerechneSchrittweite();
        }

        #endregion


        #region Methods

        /// <summary>
        /// Gibt die ID zu Abschnitt: (spur,sektor).
        /// </summary>
        /// <param name="spur">Die Spurnummer</param>
        /// <param name="sektor">Sektornummer</param>
        /// <returns>ID zu Abschnitt: (spur,sektor).</returns>
        public int GibAbschnittsnummer(int spur, int sektor) {
            return Sektoren * spur + sektor;
        }

        /// <summary>
        /// Erzeugt eine voll ausgefüllte Action anhand der angegebenen Abschnittsnummer.
        /// </summary>
        /// <param name="abschnittsNummer">The abschnitts nummer.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// abschnittsNummer;Wert kleiner 0!
        /// or
        /// Wert größer als Anzahl Abschnitte!;abschnittsNummer
        /// </exception>
        public Action GetAction(int abschnittsNummer) {
            // Preconditions
            if (abschnittsNummer < 0)
                throw new ArgumentOutOfRangeException("abschnittsNummer", "Wert kleiner 0!");
            if (abschnittsNummer > ((this.Sektoren * this.Spuren) - 1))
                throw new ArgumentOutOfRangeException("Wert größer als Anzahl Abschnitte!", "abschnittsNummer");

            Action retVal = new Action { Abschnittsnummer = abschnittsNummer };
            this.GibSpurSektor(abschnittsNummer, out retVal.SpurNummer, out retVal.SektorNummer);

            this.winkelWusa(ref retVal);

            return retVal;
        }

        /// <summary>
        /// Gibt Spur- und Sektornummer zu spezifizierter Abschnittsnummer
        /// </summary>
        /// <param name="abschnittsNummer">Die Abschnittsnummer</param>
        /// <param name="spur">Out-Parameter, der die Spurnummer erhält.</param>
        /// <param name="sektor">Out-Parameter, der die Sektornummer erhält.</param>
        public void GibSpurSektor(int abschnittsNummer, out int spur, out int sektor) {
            sektor = abschnittsNummer % Sektoren;
            spur = abschnittsNummer / Sektoren;
        }

        /// <summary>
        /// Setzt X und Y der referenzierten AMEEAction. Sektor- und Spurnummer müssen bereits gesetzt sein.
        /// </summary>
        /// <param name="action">The action reference.</param>
        private void winkelWusa(ref Action action) {
            double winkelbreite = ZwoPI / Sektoren;
            double winkel = action.SektorNummer * winkelbreite;
            float length = SpurRadien[action.SpurNummer];
            float breite = length - ((action.SpurNummer <= 0) ? 0 : SpurRadien[action.SpurNummer - 1]);
            length -= breite / 2f;
            action.X = length * Math.Cos(winkel);
            action.Y = length * Math.Sin(winkel);
        }

        /// <summary>
        /// Die die Eigenschaft 'Zielrichtung' (Sektornummer) anhand des gegebenen Vektors, der ausgehend
        /// vom Mittelpunkt dieser Karte (des Agenten) die Richtung vorgibt.<para>
        /// Grenzbereiche: ...
        /// </para>
        /// </summary>
        /// <param name="agentZiel">Vektor, der ausgehend vom Mittelpunkt dieser Karte (des Agenten) die Richtung vorgibt.</param>
        /// <returns>ID-Nummer des Sektors, der in Zielrichtung liegt.</returns>
        /// <exception cref="AMEE1Exception">Fehler bei der Berechnung der Zielrichtungssektornummer!?</exception>
        internal int GetZielrichtung(Vector agentZiel) {
            double richtungsWinkel = Math.Atan2(agentZiel.Y, agentZiel.X);
            if (richtungsWinkel < 0) {
                richtungsWinkel += ZwoPI;
            }
            double faktor = richtungsWinkel / this.Sektorbreite;
            int sektorNr = (int)(faktor + 0.5);

            if (sektorNr < 0 || sektorNr > this.Sektoren) {
                throw new AMEE1Exception("Fehler bei der Berechnung der Zielrichtungssektornummer!?");
            }
            return sektorNr;
        }

        internal int GetZielentfernung(Vector agentZiel) {
            return (int)(agentZiel.Length / Schrittweite);
        }

        double BerechneSchrittweite() {
            float kartenradius = SpurRadien[SpurRadien.Length-1];
            float rad = SpurRadien.Length > 1 ? SpurRadien[SpurRadien.Length-2] : 0.0f;
            float spurweite = kartenradius - rad;
            return kartenradius - (spurweite / 2.0f);
        }

        #endregion
    }
    
}
